package fr.jade.fraclite.factory;

import java.lang.reflect.Proxy;
import java.util.Map;

import org.objectweb.fractal.api.Component;
import org.objectweb.fractal.api.Interface;
import org.objectweb.fractal.api.Type;
import org.objectweb.fractal.api.factory.GenericFactory;
import org.objectweb.fractal.api.factory.InstantiationException;
import org.objectweb.fractal.api.type.ComponentType;
import org.objectweb.fractal.api.type.InterfaceType;
import org.objectweb.fractal.api.type.TypeFactory;

import fr.jade.fraclite.GenericComponentInterface;
import fr.jade.fraclite.InitializationContext;
import fr.jade.fraclite.ItfDesc;
import fr.jade.fraclite.FracLiteConfig;
import fr.jade.fraclite.api.ComponentInterface;
import fr.jade.fraclite.api.Controller;

/**
 * Provides a basic implementation of the {@link GenericFactory} interface.
 * <br>
 * <br>
 * <b>Requirements</b>
 * <ul>
 * <li>the types used in the {@link #newFcInstance newFcInstance} method must
 * be instances of {@link ComponentType}.</li>
 * <li>the controller descriptors used in the {@link #newFcInstance
 * newFcInstance} method must be strings referencing "true descriptors", which
 * are loaded with the {@link #_this_weaveableL} loader. Here "true descriptors"
 * means controller descriptors as specified in {@link
 * org.objectweb.fractal.julia.asm.ContextClassGenerator}.</li>
 * </ul>
 */

public class BasicGenericFactory implements Controller, GenericFactory {

  protected ClassLoader loader;
  
  // -------------------------------------------------------------------------
  // PUBLIC constructor and weaveableTF are needed for bootstrap
  // -------------------------------------------------------------------------

  public BasicGenericFactory () {
    loader = this.getClass().getClassLoader();
  }
  
  
  
  public TypeFactory weaveableTF;
  
  /**
   * Initializes the fields of this mixin and then calls the overriden method.
   * 
   * @param ic information about the component to which this controller object
   *      belongs.
   * @throws InstantiationException if the initialization fails.
   */

  public void initFcController (final InitializationContext ic) throws InstantiationException {
    weaveableTF = (TypeFactory) ic.getInterface("type-factory");
  }
  
  public Component newFcInstance (final Type type, final Object controllerDescLabel, final Object contentDesc) throws InstantiationException {
    InitializationContext ic = new InitializationContext();
    ic.type = type;
    createContent(contentDesc, ic);
    createMembrane(type, controllerDescLabel, ic);
    initControllers(ic);
    initContent(ic);
    return (Component)ic.getInterface("component");
  }

  protected void initContent(InitializationContext ic) {
    // void for the moment
  }

  protected void initControllers(InitializationContext ic) throws InstantiationException {
    for(Interface controller : ic.controllers){
      Controller ctrlImpl = (Controller) ((ComponentInterface)controller).getFcItfImpl();
      ctrlImpl.initFcController(ic);
    }
  }
  
  protected void createContent(Object contentDesc, InitializationContext ic) throws InstantiationException {
    assert contentDesc == null || contentDesc instanceof String :"contentDesc must be instance of String";
  
    if(contentDesc == null){
      ic.content = null;
    }else{
      ic.content = newImplObject((String) contentDesc);
    }
  }

  protected void createMembrane(Type componentType, Object controllerDescLabel, InitializationContext ic) throws InstantiationException {
    assert controllerDescLabel instanceof String  :"controllerDescLabel must be instance of String";
    assert componentType instanceof ComponentType :"type must be instance of ComponentType";
  
    // create component interface
    String itfImplClass = FracLiteConfig.getComponentInterfaceImpl();
    InterfaceType itfType = weaveableTF.createFcItfType("component", "org.objectweb.fractal.api.Component", false, false, false);
    Object ctrlImpl = newImplObject(itfImplClass);
    Component owner = (Component) newInterfaceObject(itfType, null, false, ctrlImpl);
    ic.controllers.add((Interface)owner);
    ic.interfaces.put("component",(Interface)owner);
    // create controllers
    Map<ItfDesc, String> controllerDesc = FracLiteConfig.getControllerDesc((String)controllerDescLabel);
    for(Map.Entry<ItfDesc, String> ctrl : controllerDesc.entrySet()){
      String itfSignature = ctrl.getKey().signature;
      String itfName = ctrl.getKey().name;
      itfImplClass = ctrl.getValue();
      itfType = weaveableTF.createFcItfType(itfName, itfSignature, false, false, false);
      ctrlImpl = newImplObject(itfImplClass);
      Interface c = (Interface) newInterfaceObject(itfType, owner, false, ctrlImpl);
      ic.controllers.add(c);
      ic.interfaces.put(itfName,c);
    }
    // create functional interfaces
    for(InterfaceType it : ((ComponentType)componentType).getFcInterfaceTypes()){
      Interface itf = (Interface) newInterfaceObject(it, owner, false, ic.content);
      ic.interfaces.put(it.getFcItfName(),itf);
      InterfaceType complementaryItfType = getComplementaryInterfaceType(it);
      itf = (Interface) newInterfaceObject(complementaryItfType, owner, true, null);
      ic.internalInterfaces.put(it.getFcItfName(), itf);
    }
    
  }

  protected InterfaceType getComplementaryInterfaceType(InterfaceType itfType) throws InstantiationException {
    return weaveableTF.createFcItfType( itfType.getFcItfName(),
                                        itfType.getFcItfSignature(),
                                        !itfType.isFcClientItf(),
                                        itfType.isFcOptionalItf(),
                                        itfType.isFcCollectionItf()
                                       );
  }

  private Object newImplObject(String contentDesc) throws InstantiationException {
    try {
      Class contentClass = loader.loadClass((String)contentDesc);
      return contentClass.newInstance();
    } catch (ClassNotFoundException e) {
      throw new ChainedInstantiationException(e,"Content class not found: " + contentDesc);
    } catch (java.lang.InstantiationException e) {
      throw new ChainedInstantiationException(e,"InstantiationException: (" + contentDesc + ")");
    } catch (IllegalAccessException e) {
      throw new ChainedInstantiationException(e,"IllegalAccessException: (" + contentDesc + ")");
    }
  }

  protected Object newInterfaceObject(InterfaceType type, Component owner, boolean isInternal, Object impl) throws InstantiationException {
    try {
      Class itfClass = loader.loadClass(type.getFcItfSignature());
      Class bciClass = ComponentInterface.class;
      Class[] itfs = new Class[] {bciClass, itfClass};
      return Proxy.newProxyInstance(loader, itfs, new GenericComponentInterface(owner,type.getFcItfName(),type,isInternal,impl));
    } catch (ClassNotFoundException e) {
      throw new InstantiationException("Controller interface class not found: " + type.getFcItfSignature());
    }
    
  }

}
